#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include <unistd.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <sys/socket.h>

#include <sys/epoll.h>


//基于epoll实现的http服务器


void Handle_events(int epfd,epoll_event events[],int maxevent,int listen_socket)
{
  if(events == NULL || maxevent <=0)
  {
    return;
  }

  //因为执行到这一步，说明一定有事件就绪
  //我们只需要关心是哪类事件就绪
  int i = 0;
  for(i = 0; i < maxevent; ++i)
  {
    char buf[1024 * 10] = {0};
    if(events[i].data.fd == listen_socket && (events[i].events & EPOLLIN))
    {
      //(a)说明listen_socket已经就绪  
      sockaddr_in peer;
      socklen_t peer_len = sizeof(peer);
      int acc_sock = accept(listen_socket,(sockaddr *)&peer,&peer_len); 
      if(acc_sock < 0)
      {
        perror("accept");
        continue;
      }
      else
      {
        //将已经建立连接的文件描述符加入到epfd中,并且设置为关心读事件
        //让epoll_wait()监听该文件的读事件就绪
        epoll_event event;
        event.data.fd = acc_sock;
        event.events = EPOLLIN;

        int ret = epoll_ctl(epfd,EPOLL_CTL_ADD,acc_sock,&event);
        if(ret < 0)
        {
          perror("epoll_ctl add");
          continue;
        }
      }
      continue;
    }

    else
    {
      //(b)先关心读事件
      if(events[i].events & EPOLLIN)
      {
        //这里只读取一次，但是这样其实是不安全的，因为一次并不能保证把缓冲区所有是数据都读走
        //有可能造成粘包问题
        ssize_t read_size =  read(events[i].data.fd,buf,sizeof(buf)-1);      
        if(read_size < 0)
        {
          perror("read");
          close(events[i].data.fd);
          epoll_ctl(epfd,EPOLL_CTL_DEL,events[i].data.fd,NULL);
          continue;
        }
        if(read_size == 0)
        {
          printf("client sidconnect!\n");
          close(events[i].data.fd);
          epoll_ctl(epfd,EPOLL_CTL_DEL,events[i].data.fd,NULL);
          continue;
        }
        buf[read_size] = '\0';
        printf("%s",buf);
        //将已经读完的文件描述符加入到epfd中,并且设置为关心写事件
        //让epoll_wait()监听该文件的写事件就绪
        epoll_event event;
        event.data.fd = events[i].data.fd;
        event.events = EPOLLOUT;
        epoll_ctl(epfd,EPOLL_CTL_ADD,events[i].data.fd,&event);
      }
      else
      {
        //(c)再关心写事件
        if(events[i].events & EPOLLOUT)
        {
          ///这里对应所有的响应都恢复一个html页面
          const char * recv = "HTTP/1.1 200 OK\r\n\r\n<html><h1>hello world</h1><html>"; 
          write(events[i].data.fd,recv,strlen(recv));
          //短连接，一次响应之后将连接断开
          close(events[i].data.fd);
          epoll_ctl(epfd,EPOLL_CTL_DEL,events[i].data.fd,NULL);
        }

      }

    }
  }
}


//启动服务器
int Server_Start(const char * ip,const short port)
{
  int sock = socket(AF_INET,SOCK_STREAM,0);

  if(sock < 0)
  {
    perror("socket");
    return -1;
  }

  sockaddr_in addr;
  addr.sin_family = AF_INET;
  addr.sin_addr.s_addr = inet_addr(ip);
  addr.sin_port = htons(port);

  int ret = bind(sock,(sockaddr *)&addr,sizeof(addr));
  if(ret < 0)
  {
    perror("bind");
    return -1;
  }

  ret = listen(sock ,5);
  if(ret < 0)
  {
    perror("listen");
    return -1;
  }
  return sock;
}



//main()函数
int main(int argc,char * argv[])
{
  //判断命令行参数
  if(argc != 3)
  {
    printf("Usage :./server [ip] [port]\n");
    return 1;
  }

  //1.启动服务器
  int listen_socket = Server_Start(argv[1],atoi(argv[2]));
  if(listen_socket < 0)
  {
    printf("server start faild\n");
    return 2;
  }

  printf("server start ok\n");

  //2.开始事件循环

  //(a)构造epoll对象
  int epfd = epoll_create(256);
  if(epfd < 0)
  {
    perror("epoll_create");
    return 3;
  }

  //(b)注册事件
  epoll_event event;//定义events结构
  event.data.fd = listen_socket;//将用户要监听的文件描述符赋值
  event.events = EPOLLIN;//为该文件描述符只注册读事件
  int ret = epoll_ctl(epfd,EPOLL_CTL_ADD,listen_socket,&event);
  if(ret < 0)
  {
    perror("epoll_ctl add\n");
    return 4;
  }
  //(c)进行循环处理
  while(1)
  {
    //调用epoll_wait()开始监听
    epoll_event events[128];
    int size = epoll_wait(epfd,events,sizeof(events)/sizeof(events[0]),-1);//超时时间为1s监听

    switch(size)
    { 
      case -1:
        perror("epoll_wait");
        break;
      case 0:
        printf("time out\n");
        break;
      default://返回值大于0 的说明一定有事件就绪了
        Handle_events(epfd,events,sizeof(events)/sizeof(events[0]),listen_socket);
        break;
    }
      perror("epoll_wait");
      printf("time out\n");

  }//end while()

  close(listen_socket);
  close(epfd);
  
}//end main()
